Show Code
library('dplyr')
library('ggplot2')
library('magrittr')
library('ggthemes')
library("plotly")
library('knitr')
library('ggiraph')
trade <- read.csv(file = "Trade.csv", header = TRUE)
# knitr::kable(head(trade))Original study: The Political Determinants of International Trade: The Major Powers, 1907–1990 (James D. Morrow, Randolph M. Siverson and Tressa E. Tabares)
Data can be accessed on Harvard Dataverse.
It is hard to deny that politics influences international trade. But which geopolitical factors matter the most? In their study, The Political Determinants of International Trade, James D. Morrow, Randolph M. Siverson, and Tressa E. Tabares explore this question.
Using 20th-century data on trade volumes among major political powers, the authors test three key arguments about the relationship between politics and trade:
Political allies trade more: States with aligned interests are more likely to engage in trade.
Democracies trade more: Democratic states are more inclined to trade with one another than with non-democracies.
Political rivalry reduces trade: Rivalries, especially in a bipolar system, hinder trade flows between states.
The dataset includes annual observations of trade flows between all directed pairs of major powers (dyads) from 1907 to 1990, excluding the years during the two world wars and the year immediately following each war. The major powers include the United States, the United Kingdom, France, Germany, Russia and Italy. Since the dataset observed both multiple units and a prolonged period of time, it presents the case of a time-series cross-sectional data (TSCS).
The dataset is prepared specifically for regression analysis and structured for time-series cross-sectional modeling. The dependent variable, trade flows (XIJL), and key independent variables, such as GDP (GNPIL and CGNPJL), population (POPIL and POPJL), and distance (DISTANCL), are presented in log-transformed form.
The dataset also includes political variables of interest:
MIDL: A binary indicator for the presence of militarized disputes within the dyad.
DEMDL: A binary indicator for whether both members of the dyad are democracies.
ALLIAL, MULTIALL, BIALL: Binary indicators representing the existence of alliances (ALLIAL), with distinctions between multipolar (MULTIALL) and bipolar (BIALL) geopolitical systems.
TAUL: A measure of similarity in the foreign policy interests of the dyad, scaled between 0 and 1.
library('dplyr')
library('ggplot2')
library('magrittr')
library('ggthemes')
library("plotly")
library('knitr')
library('ggiraph')
trade <- read.csv(file = "Trade.csv", header = TRUE)
# knitr::kable(head(trade))## create the categorical variables based on years
periods = c("Pre-WW1", "20s", "30s", "Post-WW2+50s", "60s", "70s", "80s")
trade <- trade %>%
mutate(
DECADE = case_when(YEAR <= 1913 ~ "Pre-WW1",
YEAR >= 1920 & YEAR < 1930 ~ "20s",
YEAR >= 1930 & YEAR <= 1939 ~ "30s",
YEAR >= 1947 & YEAR < 1960 ~ "Post-WW2+50s",
YEAR >= 1960 & YEAR < 1970 ~ "60s",
YEAR >= 1970 & YEAR < 1980 ~ "70s",
YEAR >= 1980 & YEAR <= 1990 ~ "80s",
TRUE ~ NA_character_
),
DECADE = factor(DECADE, levels = periods)
) %>%
mutate(YEAR = as.factor(YEAR))
trade %>%
count(DECADE) %>%
kable(
caption = "Observations by Decade",
col.names = c("Decade", "Count"),
align = "lc"
)| Decade | Count |
|---|---|
| Pre-WW1 | 294 |
| 20s | 420 |
| 30s | 420 |
| Post-WW2+50s | 546 |
| 60s | 420 |
| 70s | 420 |
| 80s | 462 |
trade_summary <- trade %>%
group_by(DECADE) %>%
summarise(total_exports = sum(XIJL, na.rm = TRUE))
export_volume <- trade_summary %>%
ggplot(aes(x = DECADE, y = total_exports)) +
geom_col(fill = 'lightblue', color = 'navy') +
scale_x_discrete(guide = guide_axis(angle = 20)) +
theme_stata() +
labs(
x = "Decade",
y = "Total Exports (million $)",
title = "Total Trade Volume by Decade"
)
ggplotly(export_volume)One of the key assumptions tested in the study is that states with similar political regimes—particularly democratic dyads—are more likely to engage in trade. This hypothesis suggests that shared democratic values foster trust, reduce transaction costs, and create a favorable environment for trade partnerships.
In the study authors code a dyad democratic if both trading states were ranked 6+ on Gurr’s Institutionalized Democracy scale.
trade <- trade %>%
mutate(Dem = case_when(DEMDL == 0 ~ "Non-democratic dyad",
DEMDL != 0 ~ "Democratic dyad"))
trade %>%
count(Dem) %>%
kable(
caption = "Number of observations per dyad type",
col.names = c("Decade", "Count"),
align = "lc"
)| Decade | Count |
|---|---|
| Democratic dyad | 1554 |
| Non-democratic dyad | 1428 |
dyad_summary <- trade %>%
group_by(DECADE, Dem) %>%
summarise(total_exports = sum(XIJL, na.rm = TRUE))
volume_dyads <- ggplot(data = dyad_summary,
mapping = aes(x = DECADE,
y = total_exports, fill = Dem)) +
geom_col(position = "dodge") +
scale_x_discrete(guide = guide_axis(angle = 20), limits=periods) +
theme_stata() +
labs(x = "Decade",
y = "Exports, constant mln $",
title = "Trade volume: Democratic partners vs. others")
ggplotly(volume_dyads)dembar_faceted <- ggplot(data = dyad_summary,
mapping = aes(x = total_exports, y = DECADE, color = Dem)) +
geom_col() +
theme_stata() +
labs(x = "Exports, constant mln $",
y = "Decades",
title = "Exports of dyads") +
facet_wrap( ~ Dem,
nrow = 1) +
scale_y_discrete(guide = guide_axis(angle = 30), limits=periods) +
theme(legend.position='none')
ggplotly(dembar_faceted)It is hypothesized that states with poor political relations trade less, as trading with a rival state may be seen as counterproductive and potentially empowering an opponent. Since “rivalry” lacks a direct measure, the authors use a binary proxy variable to indicate whether the states in a dyad experienced militarized interstate disputes.
These disputes are recorded as occurring if at least one member of the dyad threatened to use military force against the other.
trade <- trade %>%
mutate(Disputes = case_when(MIDL == 0 ~ "No disputes",
MIDL != 0 ~ "Disputes occured"))
trade %>%
count(Disputes) %>%
kable(
caption = "Number of observations per dyad type",
col.names = c("Dispute occured?", "Count"),
align = "lc"
)| Dispute occured? | Count |
|---|---|
| Disputes occured | 118 |
| No disputes | 2864 |
tradeMID_year <- trade %>%
group_by(Disputes, YEAR) %>%
summarise(total_exports = sum(XIJL, na.rm = TRUE))
MID <- ggplot(data = tradeMID_year,
mapping = aes(x = YEAR, y = total_exports, color = Disputes, group = Disputes)) +
geom_line(size = 1.2) +
theme_stata() +
labs(
x = "Year",
y = "Volume of exports",
title = "Exports of dyads: Do political conflicts affect trade?",
color = "Dispute Status"
) +
theme(axis.text.x = element_text(angle = 90, hjust = 0.5),
legend.position = 'bottom')
ggplotly(MID)tradeMID_DECADE <- trade %>%
group_by(Disputes, DECADE) %>%
summarise(total_exports = sum(XIJL, na.rm = TRUE))
MID_DECADE <- ggplot(data = tradeMID_DECADE,
mapping = aes(x = DECADE, y = total_exports, fill = Disputes, color = Disputes)) +
geom_col() +
theme_stata() +
labs(x = "Decades",
y = "Volume of exports",
title = "Exports of dyads: Do political conflicts affect trade?") +
facet_wrap( ~ Disputes, nrow = 1) +
scale_x_discrete(guide = guide_axis(angle = 30), limits=periods) +
theme(legend.position='none')
ggplotly(MID_DECADE)It is commonly argued that states with similar political interests are more likely to trade with each other. Shared interests are expected to incentivize states to engage in economic collaboration, leading to more intensive trade relations.
To measure the similarity of interests, the authors use the TAU indicator, which is based on the correlation of their alliance portfolios. TAU ranges from -1 (indicating complete dissimilarity in alliance portfolios) to 1 (indicating perfect similarity in alliance portfolios).
TAU_hist <- ggplot(trade, aes(x = TAUL)) +
geom_histogram(bins = 15, fill = "navy", color = "black", alpha = 0.7) +
labs(x = "TAU Value",
y = "Frequency",
title = "Distribution of TAU (Common Interests) Values in the Dataset") +
theme_stata()
ggplotly(TAU_hist)A low TAU value means the two countries in the dyad have very different allies, so they likely have conflicting interests. For example, one might have military agreements with certain countries, while the other has no such agreements, showing they are not closely aligned. A high TAU value means the countries share many of the same allies, indicating they have similar interests and are more likely to cooperate with each other.
As we can see on a histogram, the largest portion of the sample has relatively low similarity of allies portfolio (~ 0.2 - 0.3)
TAU_hist <- ggplot(data = trade, aes(x = TAUL, y = XIJL)) +
geom_point(alpha = 0.5) +
geom_smooth(method = "loess", color = "navy", se = FALSE) +
# geom_smooth(method = "loess", color = "navy", se = TRUE, fill = "lightblue", level = 0.90)
theme_minimal() +
labs(x = "Similarity of Interests (TAU)", y = "State Exports, mln. constant U.S. dollars)",
title = "Similarity of Interests Vs. Export Volume")
ggplotly(TAU_hist)Countries that are military allies often have stronger economic ties. When two countries are part of the same military alliance, they are expected to trust each other and collaborate more, reducing political risks that might otherwise hinder trade.
The authors use a binary variable to indicate whether countries in a trading dyad have a formal alliance or not. Additionally, they introduce an interaction term to account for the global power structure, distinguishing between a Multipolar world (before World War II) and a Bipolar world (after World War II). Hypothetically, alliances in a multipolar world are not expected to significantly impact trade, whereas alliances in a bipolar world are thought to have a stronger positive effect.
trade <- trade %>%
mutate(
Alliance = case_when(
ALLIAL == 0 ~ "No alliance",
ALLIAL != 0 ~ "Alliance"
),
Period = case_when(
as.numeric(as.character(YEAR)) < 1947 ~ "Pre-WW2",
as.numeric(as.character(YEAR)) >= 1947 ~ "Post-WW2"
)
)
alliance_summary <- trade %>%
group_by(Alliance, Period) %>%
summarize(count = n(), .groups = "drop")alliance_summary$Period <- factor(alliance_summary$Period, levels = c("Pre-WW2", "Post-WW2"))
ALLIAL_barplot <- ggplot(alliance_summary, aes(x = Period, y = count, fill = Alliance)) +
geom_bar(stat = "identity") +
scale_fill_manual(values = c("lightblue", "steelblue")) +
labs(title = "Alliance: Dyad observations before and after WW2",
x = "Period", y = "Count of Dyads") +
theme_stata() +
theme(legend.title = element_text(size = 12),
legend.text = element_text(size = 10))
ggplotly(ALLIAL_barplot)alliance_volume <- trade %>%
group_by(YEAR, Alliance) %>%
summarize(avg_exports = mean(XIJL, na.rm = TRUE), .groups = "drop")
alliance_volume$YEAR <- as.numeric(as.character(alliance_volume$YEAR))
ALLIAL_volume <- ggplot(alliance_volume, aes(x = YEAR, y = avg_exports, color = Alliance, group = Alliance)) +
geom_line() +
geom_vline(xintercept = c(1939, 1947), linetype = "dashed", color = "red", size = 1)
# geom_rect(aes(xmin = 1939, xmax = 1947, ymin = -Inf, ymax = Inf),
# fill = "red", alpha = 0.2) +
labs(title = "Time Series of Exports by Alliance Status",
x = "Year",
y = "Exports (in millions of USD)",
color = "Alliance Status") +
scale_color_manual(values = c("lightblue", "steelblue")) +
theme_stata() +
theme(axis.text.x = element_text(angle = 90, hjust = 0.5),
legend.position = 'bottom')NULL
ggplotly(ALLIAL_volume)library(ggplot2)
library(ggiraph)
# Create the basic line plot with interactive tooltips
ALLIAL_volume <- ggplot(alliance_volume, aes(x = YEAR, y = avg_exports, color = Alliance, group = Alliance)) +
geom_line_interactive(aes(tooltip = paste("Year: ", YEAR, "<br>Exports: ", round(avg_exports, 2))), size = 1) + # Add interactive tooltips to lines
labs(title = "Time Series of Exports by Alliance Status",
x = "Year",
y = "Exports (in millions of USD)",
color = "Alliance Status") +
scale_color_manual(values = c("lightblue", "steelblue")) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, hjust = 0.5),
legend.position = 'bottom') +
# Add vertical lines at 1937 and 1947
geom_vline(xintercept = c(1937, 1947), linetype = "dashed", color = "red", size = 1)
# Convert the plot to an interactive ggiraph plot
interactive_plot <- girafe(ggobj = ALLIAL_volume)
# Show the plot
interactive_plotFor testing the hypotheses, the authors employ a gravity model of international trade, augmented with variables for the political determinants discussed earlier (democracy, rivalry, alliances, and similarity of interests). The gravity model is a well-established framework in economics that predicts the volume of trade flows between two countries based on three core components:
Economic Size:
Measured as Gross National Product (GNP) in this study, economic size captures the production and consumption potential of the trading countries.
Larger economies are expected to trade more due to their greater capacity to produce goods and services (supply side) and their higher consumer demand (demand side).
Physical Distance:
Distance acts as a proxy for trade costs, including transportation expenses, communication barriers, and time lags.
Greater distances typically reduce trade flows, as higher costs make trade less competitive.
Population:
The authors include population to account for its dual role:
Supply Side: Larger populations may increase production capacity, especially in labor-intensive industries.
Demand Side: Larger domestic markets may reduce the need for trade, as countries with sizable populations can meet more of their needs internally.
In this study, population is expected to negatively correlate with trade, reflecting the potential self-sufficiency of larger nations.
The authors extend the model by introducing political variables to examine how non-economic factors influence trade flows. These additions enable the gravity model to test hypotheses about how democracy, political rivalry, alliances, and shared interests shape international economic relationships.
# Fit a regression model
model <- lm(XIJL ~ GNPIL + CGNPJL, data = trade)
# grid of values for GNPIL and CGNPJL
grid <- expand.grid(
GNPIL = seq(min(trade$GNPIL, na.rm = TRUE), max(trade$GNPIL, na.rm = TRUE), length.out = 50),
CGNPJL = seq(min(trade$CGNPJL, na.rm = TRUE), max(trade$CGNPJL, na.rm = TRUE), length.out = 50)
)
# Predict trade volume for the grid
grid$XIJL <- predict(model, newdata = grid)
# Create a matrix for the surface
surface_matrix <- matrix(grid$XIJL, nrow = 50, ncol = 50)
# Add the surface to the 3D scatterplot
plot_ly() %>%
add_markers(data = trade, x = ~GNPIL, y = ~CGNPJL, z = ~XIJL,
type = 'scatter3d', mode = 'markers',
marker = list(size = 3, color = ~XIJL, colorscale = 'Viridis')) %>%
add_surface(x = ~unique(grid$GNPIL), y = ~unique(grid$CGNPJL), z = ~surface_matrix,
colorscale = 'Blues', opacity = 0.5) %>%
layout(scene = list(
xaxis = list(title = "GNP of Exporter"),
yaxis = list(title = "GNP of Importer"),
zaxis = list(title = "Trade Volume")))library(patchwork)
scatter_exporter <- ggplot(trade, aes(x = GNPIL, y = XIJL)) +
geom_point_interactive(aes(tooltip = paste("Exporter GNP:", GNPIL, "<br>Trade Volume:", XIJL)),
alpha = 0.6, color = "darkblue") +
geom_smooth(method = "lm", color = "blue", se = FALSE) +
labs(x = "GNP of Exporter", y = "Trade Volume (XIJL)",
title = "Trade Volume vs Exporter GNP") +
theme_minimal()
scatter_importer <- ggplot(trade, aes(x = CGNPJL, y = XIJL)) +
geom_point_interactive(aes(tooltip = paste("Importer GNP:", CGNPJL, "<br>Trade Volume:", XIJL)),
alpha = 0.6, color = "darkred") +
geom_smooth(method = "lm", color = "red", se = FALSE) +
labs(x = "GNP of Importer", y = "Trade Volume (XIJL)",
title = "Trade Volume vs Importer GNP") +
theme_minimal()
girafe(ggobj = scatter_exporter + scatter_importer, width_svg = 10, height_svg = 5)# Create the scatterplot
scatter_distance <- ggplot(trade, aes(x = DISTANCL, y = XIJL)) +
geom_point_interactive(aes(tooltip = paste("Distance:", DISTANCL, "<br>Trade Volume:", XIJL)),
alpha = 0.6, color = "blue") +
geom_smooth(method = "lm", color = "darkblue", se = TRUE) +
labs(x = "Log-Distance", y = "Trade Volume (XIJL)",
title = "Distance vs Trade Volume") +
theme_minimal()
# scatter_distance
girafe(ggobj = scatter_distance, width_svg = 10, height_svg = 5)# Scatterplot: XIJL vs Exporter Population
scatter_exporter_pop <- ggplot(trade, aes(x = POPIL, y = XIJL)) +
geom_point(alpha = 0.6, color = "darkblue") +
geom_smooth(method = "lm", color = "blue", se = FALSE) +
labs(x = "Exporter Population", y = "Trade Volume (XIJL)",
title = "Trade Volume vs Exporter Population") +
theme_minimal()
# Scatterplot: XIJL vs Importer Population
scatter_importer_pop <- ggplot(trade, aes(x = POPJL, y = XIJL)) +
geom_point(alpha = 0.6, color = "darkred") +
geom_smooth(method = "lm", color = "red", se = FALSE) +
labs(x = "Importer Population", y = "Trade Volume (XIJL)",
title = "Trade Volume vs Importer Population") +
theme_minimal()
# Combine the two scatterplots side by side
girafe(ggobj = scatter_exporter_pop + scatter_importer_pop, width_svg = 10, height_svg = 5)colSums(is.na(trade)) YEAR DYAD XIJL GNPIL POPIL POPJL ALLIAL DEMDL
0 0 102 126 60 60 0 0
DISTANCL MIDL CGNPJL TAUL STIN BIALL MULTIALL DECADE
0 0 126 12 0 0 0 0
Dem Disputes Alliance Period
0 0 0 0
colMeans(is.na(trade)) * 100 YEAR DYAD XIJL GNPIL POPIL POPJL ALLIAL DEMDL
0.0000000 0.0000000 3.4205231 4.2253521 2.0120724 2.0120724 0.0000000 0.0000000
DISTANCL MIDL CGNPJL TAUL STIN BIALL MULTIALL DECADE
0.0000000 0.0000000 4.2253521 0.4024145 0.0000000 0.0000000 0.0000000 0.0000000
Dem Disputes Alliance Period
0.0000000 0.0000000 0.0000000 0.0000000
datatable(
missing_summary,
caption = “Missing Values Summary”,
options = list(pageLength = 10, autoWidth = TRUE)
)
missing_summary <- data.frame(
Variable = names(trade),
Missing_Count = colSums(is.na(trade)),
Missing_Percentage = colMeans(is.na(trade)) * 100
)
missing_summary %>%
arrange(desc(Missing_Count)) %>%
knitr::kable(
format = "html",
caption = "Missing Values Summary",
col.names = c("Variable", "Missing Count", "Missing Percentage (%)"),
digits = 2
)| Variable | Missing Count | Missing Percentage (%) | |
|---|---|---|---|
| GNPIL | GNPIL | 126 | 4.23 |
| CGNPJL | CGNPJL | 126 | 4.23 |
| XIJL | XIJL | 102 | 3.42 |
| POPIL | POPIL | 60 | 2.01 |
| POPJL | POPJL | 60 | 2.01 |
| TAUL | TAUL | 12 | 0.40 |
| YEAR | YEAR | 0 | 0.00 |
| DYAD | DYAD | 0 | 0.00 |
| ALLIAL | ALLIAL | 0 | 0.00 |
| DEMDL | DEMDL | 0 | 0.00 |
| DISTANCL | DISTANCL | 0 | 0.00 |
| MIDL | MIDL | 0 | 0.00 |
| STIN | STIN | 0 | 0.00 |
| BIALL | BIALL | 0 | 0.00 |
| MULTIALL | MULTIALL | 0 | 0.00 |
| DECADE | DECADE | 0 | 0.00 |
| Dem | Dem | 0 | 0.00 |
| Disputes | Disputes | 0 | 0.00 |
| Alliance | Alliance | 0 | 0.00 |
| Period | Period | 0 | 0.00 |
trade_clean <- na.omit(trade)library(stargazer)
gmt_full <- lm(XIJL ~ MIDL + TAUL + DEMDL + MULTIALL + BIALL + GNPIL + POPIL + CGNPJL + POPJL + DISTANCL, data = trade_clean)
stargazer(gmt_full, type = "text")
===============================================
Dependent variable:
---------------------------
XIJL
-----------------------------------------------
MIDL -1.359***
(0.172)
TAUL 2.282***
(0.234)
DEMDL 0.879***
(0.095)
MULTIALL -0.983***
(0.165)
BIALL -0.699***
(0.153)
GNPIL 0.698***
(0.032)
POPIL -0.332***
(0.070)
CGNPJL 0.514***
(0.032)
POPJL -0.428***
(0.070)
DISTANCL -0.221***
(0.028)
Constant -4.306***
(0.354)
-----------------------------------------------
Observations 2,631
R2 0.618
Adjusted R2 0.617
Residual Std. Error 1.104 (df = 2620)
F Statistic 424.504*** (df = 10; 2620)
===============================================
Note: *p<0.1; **p<0.05; ***p<0.01